package com.jorado.metrics.jvm;

import com.jorado.metrics.util.LabelHelp;
import com.jorado.metrics.util.VersionHelp;
import io.prometheus.client.Collector;
import io.prometheus.client.CounterMetricFamily;
import io.prometheus.client.GaugeMetricFamily;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Exports metrics about JVM thread areas.
 * <p>
 * Example usage:
 * <pre>
 * {@code
 *   new ThreadExports().register();
 * }
 * </pre>
 * Example metrics being exported:
 * <pre>
 *   jvm_threads_current{} 300
 *   jvm_threads_daemon{} 200
 *   jvm_threads_peak{} 410
 *   jvm_threads_started_total{} 1200
 * </pre>
 */
public class ThreadExports extends Collector {
    private final ThreadMXBean threadBean;

    public ThreadExports() {
        this(ManagementFactory.getThreadMXBean());
    }

    public ThreadExports(ThreadMXBean threadBean) {
        this.threadBean = threadBean;
    }

    void addThreadMetrics(List<MetricFamilySamples> sampleFamilies) {
        GaugeMetricFamily threadCount = new GaugeMetricFamily(
                "jvm_threads_current",
                "Current thread count of a JVM" + VersionHelp.Ver,
                Arrays.asList(LabelHelp.addLabels()));
        sampleFamilies.add(threadCount);
        GaugeMetricFamily daemonThreadCount = new GaugeMetricFamily(
                "jvm_threads_daemon",
                "Daemon thread count of a JVM" + VersionHelp.Ver,
                Arrays.asList(LabelHelp.addLabels()));
        sampleFamilies.add(daemonThreadCount);
        GaugeMetricFamily peakThreadCount = new GaugeMetricFamily(
                "jvm_threads_peak",
                "Peak thread count of a JVM" + VersionHelp.Ver,
                Arrays.asList(LabelHelp.addLabels()));
        sampleFamilies.add(peakThreadCount);
        CounterMetricFamily totalStartedThreadCount = new CounterMetricFamily(
                "jvm_threads_started_total",
                "Started thread count of a JVM" + VersionHelp.Ver,
                Arrays.asList(LabelHelp.addLabels()));
        sampleFamilies.add(totalStartedThreadCount);
        GaugeMetricFamily findDeadlockedThreads = new GaugeMetricFamily(
                "jvm_threads_deadlocked",
                "Cycles of JVM-threads that are in deadlock waiting to acquire object monitors or ownable synchronizers." + VersionHelp.Ver,
                Arrays.asList(LabelHelp.addLabels()));
        sampleFamilies.add(findDeadlockedThreads);
        GaugeMetricFamily findMonitorDeadlockedThreads = new GaugeMetricFamily(
                "jvm_threads_deadlocked_monitor",
                "Cycles of JVM-threads that are in deadlock waiting to acquire object monitors." + VersionHelp.Ver,
                Arrays.asList(LabelHelp.addLabels()));
        sampleFamilies.add(findMonitorDeadlockedThreads);
        threadCount.addMetric(Arrays.asList(LabelHelp.addValues()), threadBean.getThreadCount());
        daemonThreadCount.addMetric(Arrays.asList(LabelHelp.addValues()), threadBean.getDaemonThreadCount());
        peakThreadCount.addMetric(Arrays.asList(LabelHelp.addValues()), threadBean.getPeakThreadCount());
        totalStartedThreadCount.addMetric(Arrays.asList(LabelHelp.addValues()), threadBean.getTotalStartedThreadCount());
        findDeadlockedThreads.addMetric(Arrays.asList(LabelHelp.addValues()), nullSafeArrayLength(threadBean.findDeadlockedThreads()));
        findMonitorDeadlockedThreads.addMetric(Arrays.asList(LabelHelp.addValues()), nullSafeArrayLength(threadBean.findMonitorDeadlockedThreads()));
    }

    private static double nullSafeArrayLength(long[] array) {
        return null == array ? 0 : array.length;
    }

    public List<MetricFamilySamples> collect() {
        List<MetricFamilySamples> mfs = new ArrayList<MetricFamilySamples>();
        addThreadMetrics(mfs);
        return mfs;
    }
}
